package Client;

import java.awt.AWTEvent;
import java.awt.Color;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.net.SocketException;
import java.text.SimpleDateFormat;
import java.util.Date;

import javax.crypto.Cipher;
import javax.crypto.SealedObject;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFormattedTextField;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import javax.swing.JTextField;
import javax.swing.SwingConstants;
import javax.swing.UIManager;
import javax.swing.WindowConstants;
import javax.swing.UIManager.LookAndFeelInfo;

import misc.Hashing;
import misc.VSP3Icons;

import tickets.CipherTicket;

public class SwingClient implements ActionListener {

  private Color GREEN = new Color(0, 185, 0);
  private Color RED = new Color(200, 0, 0);

  private final String AS = "141.22.27.107";
  private final int AS_PORT = 10000;

  private final String TGS = "141.22.27.107";
  private final int TGS_PORT = 20000;

  private Socket socket = null;
  private SealedObject so = null;

  private CipherTicket Kaas;

  // 8-byte Salt
  byte[] salt = { (byte) 0xA9, (byte) 0x9B, (byte) 0xC8, (byte) 0x32, (byte) 0x56, (byte) 0x35, (byte) 0xE3, (byte) 0x03 };
  // Iteration count
  int iterationCount = 19;

  private final JTextField txtName = new JTextField("alice");
  private final JPasswordField txtPassword = new JPasswordField();
  private final JLabel lblStatus = new JLabel();
  private final JButton btConnectToBob = new JButton("Get Channel to Bob");
  private JCheckBox chkUseCustomDate = new JCheckBox();
  @SuppressWarnings("serial")
  private JFormattedTextField txtFormattedDate = new JFormattedTextField(new SimpleDateFormat("dd.MM.yyyy HH:mm:ss")) {
    @Override
    public void setEnabled(boolean enabled) {
      super.setEnabled(enabled);
      chkUseCustomDate.setEnabled(enabled);
    };
  };
  private JLabel lblCustomDate = new JLabel("Use Date", SwingConstants.RIGHT);;

  public SwingClient() {
    JFrame frame = new JFrame();
    frame.setIconImage(VSP3Icons.ICON_KERBEROS_MIT);
    frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    frame.setTitle("VSP 3 Kerberos");
    JPanel pane = new JPanel();
    pane.setLayout(new GridBagLayout());
    frame.add(pane);

    txtName.setHorizontalAlignment(SwingConstants.CENTER);
    txtName.setToolTipText("Der Benutzername, Tippe Enter fuer Action");
    txtPassword.setHorizontalAlignment(SwingConstants.CENTER);
    txtPassword.setEnabled(false);
    txtPassword.setToolTipText("Der Password ist '', Tippe Enter fuer Action");
    txtFormattedDate.setEnabled(false);
    chkUseCustomDate.setSelected(false);
    chkUseCustomDate.setEnabled(false);
    btConnectToBob.setEnabled(false);
    btConnectToBob.setToolTipText("Klicke, um den sicheren Kanal anzufragen");

    GridBagConstraints c = new GridBagConstraints();
    c.fill = GridBagConstraints.HORIZONTAL;
    c.insets = new Insets(2, 2, 2, 2);

    c.weightx = 1;
    c.gridx = 0;
    c.gridy = 0;
    pane.add(new JLabel("Name", SwingConstants.RIGHT), c);

    c.weightx = 10;
    c.gridx = 1;
    c.gridy = 0;
    pane.add(txtName, c);

    c.weightx = 1;
    c.gridx = 0;
    c.gridy = 1;
    pane.add(new JLabel("Passwort", SwingConstants.RIGHT), c);

    c.weightx = 10;
    c.gridx = 1;
    c.gridy = 1;
    pane.add(txtPassword, c);

    c.weightx = 1;
    c.gridx = 0;
    c.gridy = 2;

    pane.add(lblCustomDate, c);

    c.weightx = 10;
    c.gridx = 1;
    c.gridy = 2;
    pane.add(txtFormattedDate, c);

    c.weightx = 1;
    c.gridx = 2;
    c.gridy = 2;

    pane.add(chkUseCustomDate, c);

    c.weightx = 0.0;
    c.gridwidth = 2;
    c.gridx = 0;
    c.gridy = 3;
    pane.add(btConnectToBob, c);

    c.weightx = 1;
    c.gridwidth = 2;
    c.gridx = 0;
    c.gridy = 4;
    pane.add(new JLabel("Status"), c);

    lblStatus.setHorizontalAlignment(SwingConstants.LEFT);
    c.weightx = 10;
    c.gridwidth = 2;
    c.gridx = 1;
    c.gridy = 4;
    pane.add(lblStatus, c);

    setListeners();

    setRedStatus("Not connected");
    txtName.requestFocus();
    frame.pack();
    frame.setSize(300, frame.getHeight());
    frame.setVisible(true);
  }

  private void setListeners() {
    txtName.addActionListener(this);
    txtPassword.addActionListener(this);

    txtPassword.addKeyListener(new KeyAdapter() {
      @Override
      public void keyTyped(KeyEvent e) {
        txtPassword.setToolTipText("Der Password ist '" + new String(txtPassword.getPassword()) + e.getKeyChar() + "'");
      }
    });

    txtName.addMouseListener(new MouseAdapter() {
      @Override
      public void mouseClicked(MouseEvent e) {
        resetGui();
      }
    });

    chkUseCustomDate.addItemListener(new ItemListener() {

      @Override
      public void itemStateChanged(ItemEvent e) {
        if (chkUseCustomDate.isSelected()) {
          txtFormattedDate.setValue(new Date());
          lblCustomDate.setFont(lblCustomDate.getFont().deriveFont(Font.BOLD));
          lblCustomDate.setForeground(Color.RED);
        } else {
          lblCustomDate.setFont(lblCustomDate.getFont().deriveFont(Font.PLAIN));
          lblCustomDate.setBackground(null);
          lblCustomDate.setForeground(Color.BLACK);
        }
      }
    });

    btConnectToBob.addActionListener(this);
  }

  private void resetGui() {
    // reset
    socket = null;
    socket = null;
    so = null;
    Kaas = null;
    
    txtName.setEnabled(true);
    txtPassword.setText("");
    txtPassword.setEnabled(false);
    txtFormattedDate.setEnabled(false);
    chkUseCustomDate.setSelected(false);
    chkUseCustomDate.setEnabled(false);
    btConnectToBob.setEnabled(false);
    setRedStatus("Not Connected");
  }

  private void setRedStatus(String message) {
    lblStatus.setForeground(RED);
    lblStatus.setText(message);
    System.err.println(message);
  }

  private void setGreenStatus(String message) {
    lblStatus.setForeground(GREEN);
    lblStatus.setText(message);
    System.out.println(message);
  }

  private void doStuffForLoginTextAndOnLoginTextTabPress() {

    txtName.setEnabled(false);
    sendNameToAS(txtName.getText());
    if (socket != null) {
      // setSocketTimeout(10000);
      recieveCipherTicketFromAS();
    }
  }

  private void setSocketTimeout(int timeout) {
    try {
      socket.setSoTimeout(timeout);
    } catch (SocketException e) {
      e.printStackTrace();
    }
  }

  private void recieveCipherTicketFromAS() {

    if (null == socket) {
      System.err.println("Connection to AS was Lost..");
      return;
    }

    try {
      ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());

      String result = (String) ois.readObject();

      if (result.equals("OK")) {
        so = (SealedObject) ois.readObject();
        System.out.println("SealedObject from AS recieved");

        if (null != so) {
          txtPassword.setEnabled(true);
          txtFormattedDate.setEnabled(true);
          txtPassword.requestFocus();
          btConnectToBob.setEnabled(true);
          setGreenStatus("Recieved Ticket from AS");
        } else {
          setRedStatus("AS sent back null");
          txtName.setEnabled(true);
        }
      } else if (result.equals("NOK")) {
        setRedStatus("No such Login found");
        txtName.setEnabled(true);
      }

    } catch (IOException e) {
      e.printStackTrace();
    } catch (ClassNotFoundException e) {
      e.printStackTrace();
    }

    socket = null;
  }

  private void sendNameToAS(String login) {

    if (null == socket) {
      // try to connect to the AS
      try {
        socket = new Socket(AS, AS_PORT);
        System.out.println("Connection to AS established");
      } catch (Exception e) {
        setRedStatus("Could not connect to AS");
        txtName.setEnabled(true);
        // e.printStackTrace();
      }
    }
    // if successfully connected
    if (null != socket) {
      // Send AS the Login
      ObjectOutputStream oos = null;
      try {
        oos = new ObjectOutputStream(socket.getOutputStream());

        oos.writeObject(login);
        oos.flush();
        System.out.println("Login sent");
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }

  private void doStuffForPasswordTextAndLoginButtonOnClick() {

    txtPassword.setEnabled(false);
    txtFormattedDate.setEnabled(false);
    btConnectToBob.setEnabled(false);
    setGreenStatus("Connecting..");

    decodeKaasTicket(txtPassword.getPassword());
    if (Kaas != null) {
      sendTicketToTGS(getDateForTGSToicket());
      if (socket != null) {
        recieveAliceBobKeyFromTGS();
      }
    }
  }

  private Date getDateForTGSToicket() {
    Date date = new Date();

    if (chkUseCustomDate.isSelected()) {
      try {
        date = (Date) txtFormattedDate.getValue();
      } catch (Exception e) {
        e.printStackTrace();
      }
    }

    return date;
  }

  private void sendTicketToTGS(Date d) {

    SealedObject KatgsSO = Kaas.getCipherTicket();

    try {

      // Create Data to send
      SecretKey Katgs = Kaas.getKey();
      Cipher c = Cipher.getInstance(Katgs.getAlgorithm());
      c.init(Cipher.ENCRYPT_MODE, Katgs);
      // Date d = new Date();

      // Thread.sleep(1000);

      // Connect to TGS
      if (null == socket) {
        // try to connect to the TGS
        try {
          socket = new Socket(TGS, TGS_PORT);
          setGreenStatus("Connection to TGS established");
        } catch (Exception e) {
          setRedStatus("Could not connect to TGS");
          txtPassword.setEnabled(true);
          txtPassword.requestFocus();
          txtFormattedDate.setEnabled(true);
        }
      }
      // if connection to TGS was successful
      if (null != socket) {
        // Write Data
        ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());

        oos.writeObject(KatgsSO);
        oos.writeObject("bob");
        oos.writeObject(new SealedObject(d, c));

        oos.flush();
        System.out.println("Ticket to TGS sent");
      }

    } catch (Exception e) {
      setRedStatus("Could not send ticket to TGS");
      e.printStackTrace();
    }
  }

  private void decodeKaasTicket(char[] password) {

    try {
      SecretKey KaasKey = new SecretKeySpec(Hashing.getSHAValue128(password), "AES");
      System.out.println("passwort: UID" + SecretKey.serialVersionUID + " " + KaasKey.getEncoded());

      Kaas = (CipherTicket) so.getObject(KaasKey);
      System.out.println("CT decoded");

    } catch (Exception e) {
      setRedStatus("Wrong password!");
      txtPassword.setEnabled(true);
      txtPassword.requestFocus();
      txtFormattedDate.setEnabled(true);
    }
  }

  private void recieveAliceBobKeyFromTGS() {

    if (null == socket) {
      setRedStatus("Connection to TGS was Lost");
      return;
    }

    try {
      ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());

      String result = (String) ois.readObject();
      if (result.equals("OK")) {
        so = (SealedObject) ois.readObject();
        System.out.println("SealedObject from TGS recieved");
        setGreenStatus("OK");
      } else if (result.equals("NOK")) {
        setRedStatus("TGS could not create a Ticket");
      } else if (result.equals("BADTIME")) {
        setRedStatus("Bad timestamp");

        // so = (SealedObject) ois.readObject();
        // Date tgsTime = decodeTGSTime();
        // tgsTime.setTime(tgsTime.getTime()+2000);
        // sendTicketToTGS(tgsTime);
      } else if (result.equals("EXPIRED")) {
        setRedStatus("Session Ticket has expired");
      } else {
        setRedStatus("TGS send unknown reply:" + result);
      }
    } catch (IOException e) {
      e.printStackTrace();
    } catch (ClassNotFoundException e) {
      e.printStackTrace();
    }
  }

  public static void main(String[] args) {

    try {
      for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
        if ("Nimbus".equals(info.getName())) {
          UIManager.setLookAndFeel(info.getClassName());
          break;
        }
      }
    } catch (Exception e) {
      // If Nimbus is not available, you can set the GUI to another look and feel.
    }

    new SwingClient();
  }

  private void doAction(AWTEvent e) {
    Object source = e.getSource();
    if (source == txtName) {
      doStuffForLoginTextAndOnLoginTextTabPress();
    } else if (source == txtPassword || source == txtFormattedDate || source == btConnectToBob) {
      doStuffForPasswordTextAndLoginButtonOnClick();
    }
  }

  @Override
  public void actionPerformed(ActionEvent e) {
    doAction(e);
  }

}
